package pl.medec.formmanager 
{
	import pl.medec.formmanager.controllers.CheckboxController;
	import pl.medec.formmanager.controllers.InputController;
	import pl.medec.formmanager.controllers.TextAreaController;

	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.display.MovieClip;
	import flash.events.EventDispatcher;
	import flash.text.TextField;
	import flash.utils.Dictionary;

	/**
	 * @author Piotr Mederak
	 */
	public class FormManager extends EventDispatcher 
	{
		public static const VERSION:String = '1.0';
		
		/**
		 * Czy pierwsze pole w formularzu ma miec focus?
		 */
		public var firstItemFocus:Boolean = true;
		/**
		 * Czy trace'owac szczegolowo
		 */
		public var debugMode:Boolean = false;
		/**
		 * Jesli mamy wiele elementow po ktorych mozna przechodzic tabulatorem
		 * a tylko niektore z nich naleza do formularza
		 * mozemy okreslic poczatkowy tabIndex dla pierwszego pola w formularzu
		 * kolejne pola beda mialy tabIndex powiekszony o ilosc wczesniejszych
		 * elementow
		 */
		public var tabIndexStart:uint = 0;
		private var items:Array = new Array( );
		private var inputs:Array = new Array( );
		private var checkboxes:Array = new Array( );
		private var inputsByFields:Dictionary = new Dictionary( );		private var checkboxesByClip:Dictionary = new Dictionary( );
		private var _badItems:Array;

		public function FormManager() 
		{
		}

		/**
		 * Dodaje do formularza nowy przycisk. Np 'submit' czy 'clear'. Buttony te,
		 * nie sa analizowane podczas walidacji, natomiast dziala na nich mechanizm autofocusa,
		 * i przechodzenia pomiedzy nimi a innymi polami formularza za pomoca tabulatora
		 * 
		 * @param button InteractiveObject Dowolny obiekt, ktory obsluguje zdarzenia myszy.
		 */
		public function addButton(button:InteractiveObject):void 
		{
			if(this.firstItemFocus && this.items.length == 0) 
			{
				if(button.stage) 
				{
					button.stage.focus = button;
				}
			}        	
			this.items.push( button );
            
			button.tabEnabled = true;
			button.tabIndex = this.items.length + tabIndexStart;
		}

		/**
		 * Dodaje do formularza nowy element typu checkbox
		 * 
		 * @param checkbox MovieClip skladajacy sie zawsze z dwoch klatek. W pierwszej klatce
		 * znajduje się stan zaznaczenia, w drugiej odznaczenia. Nie jest wymagane umieszczanie
		 * jakiegokolwiek kodu wewnatrz movieclipa (w tym stop()).
		 * 
		 * @param required Boolean Czy pole jest wymagane.
		 * 
		 * @param errorInfo String/DisplayObject/Function Może przybrac trzy postaci: String, DisplayObject lub Function
		 * W przypadku blednej walidacji pola, w zaleznosci od wartosci tego parametru
		 * pokaze sie w polu jakis napis (String), parametr visible obiektu DisplayObject zostanie
		 * ustawiony na true, lub tez wywola sie funkcja. Dodanie w tym argumencie referencji 
		 * do DisplayObject sprawi, ze instancja zostanie ukryta poprzez zmiane jej
		 * wlasciwosci visible na false. I bedzie pokazywana lub ukrywana w zaleznosci od wyniku walidacji.
		 */
		public function addCheckbox(checkbox:MovieClip, required:Boolean, errorInfo:* = null):void 
		{
			var cc:CheckboxController = new CheckboxController( checkbox, required, errorInfo );
			if(this.firstItemFocus && this.items.length == 0) 
			{
				if(checkbox.stage) 
				{
					checkbox.stage.focus = checkbox;
				}
			}        	
			this.items.push( cc );
			this.checkboxes.push( cc );
			this.checkboxesByClip[checkbox] = cc;
            
			checkbox.tabEnabled = true;
			checkbox.tabIndex = this.items.length + tabIndexStart;
		}

		/**
		 * Dekonstruktor
		 */
		public function destroy():void 
		{
			var im:InputController;
			var tmp_l:uint = this.inputs.length;
			
			for(var i:uint = 0; i < tmp_l; i++) 
			{
				im = this.getInputControllerByIndex( i );
				im.destroy( );
			}
			
			this.inputs = null;
			this.checkboxes = null;
			this.inputsByFields = null;
			this.checkboxesByClip = null;
		}

		/**
		 * Dodaje do formularza nowe pole w ktore mozna wpisywac tekst
		 * 
		 * @param inputFiled Pole tekstowe
		 * 
		 * @param validator Class Klasa odpowiadajaca za walidacje - musi byc zgodna
		 * z interfacem IValidator.
		 * 
		 * @param required Boolean Czy pole jest wymagane. Jesli nie jest formularz przejdzie 
		 * pomyslnie walidacje o ile pole to bedzie puste lub bedzie mialo wartosc 
		 * domyslna (zwykle opis pola typu "wpisz e-mail"). Jesli argument jest ustawiony 
		 * na true, formularz nie doposci do pozostawiania tego pola nieuzupelnionego.
		 * 
		 * @param clearDefaultText Boolean Czy po wejsciu do pola (focus in) ma byc wyczyszczona
		 * domyslna jego wartosc czy tez ma byc pozostawiona by moc ja recznie zmodyfikowac
		 * 
		 * @param errorInfo String/DisplayObject/Function Może przybrac trzy postaci: String, DisplayObject lub Function
		 * W przypadku blednej walidacji pola, w zaleznosci od wartosci tego parametru
		 * pokaze sie w polu jakis napis (String), parametr visible obiektu DisplayObject zostanie
		 * ustawiony na true, lub tez wywola sie funkcja. Dodanie w tym argumencie referencji 
		 * do DisplayObject sprawi, ze instancja zostanie ukryta poprzez zmiane jej
		 * wlasciwosci visible na false. I bedzie pokazywana lub ukrywana w zaleznosci od wyniku walidacji.
		 * 
		 * @param errorBorderColor Number Kolor na jaki ma sie border pola tekstowego ustawic w przypadku blednej walidacji.
		 * Jesli nie chcemy by sie pokazywal border wystarczy nic nie wpisywac
		 * 
		 * @see IValidator
		 */
		public function addInputField(inputField:TextField, validator:Class, required:Boolean = false, clearDefaultText:Boolean = false, errorInfo:* = null, errorBorderColor:Number = NaN):InputController 
		{
			////trace('addInputFields: ' + inputField, ' validator: ' + validator);
			var ic:InputController;
			if(inputField.multiline) 
			{
				ic = new TextAreaController( inputField, validator, required, clearDefaultText, errorInfo, errorBorderColor );
			} 
			else 
			{
				ic = new InputController( inputField, validator, required, clearDefaultText, errorInfo, errorBorderColor );
			}
            
			if(this.firstItemFocus && this.items.length == 0) 
			{
				if(inputField.stage) 
				{
					inputField.stage.focus = inputField;
				}
			}
			this.items.push( ic );
			this.inputs.push( ic );
			this.inputsByFields[inputField] = ic;
			
			inputField.tabEnabled = true;
			inputField.tabIndex = this.items.length + tabIndexStart;
			
			return ic;
		}

		/**
		 * Zwraca ilosc elementow w formularzu
		 */
		public function get tabIndexCounter():uint 
		{
			return this.inputs.length;
		}

		/**
		 * @deprecated Pozostawione by zachowac kompatybilnosc wstecz. Nie zaleca
		 * sie uzywania tej metody gdyz moze ona zniknac w ktorejs z kolejnych
		 * wersji
		 */
		public function brutalTextFieldValid(textField:TextField, isValid:Boolean):void 
		{
			this.getInputControllerByField( textField ).validate( true, isValid );			
		}

		/**
		 * Zwraca zawartosc pola tekstowego. W przeciwienstwie do field.text nie zwraca
		 * domyslnej wartosci pola lub komunikatu bledu o ile takowy jest aktualnie
		 * wyswietlony w polu. 
		 */
		public function getTextInputTextField(field:TextField):String 
		{
			return this.getInputControllerByField( field ).getInputText( );
		}

		/**
		 * Zwraca domyslna zawartosc pola tekstowego. 
		 */
		public function getDefaultTextFromTextField(email_tf:TextField):String 
		{
			return this.getInputControllerByField( email_tf ).defaultDescription;
		}

		public function getCheckboxControllerByIndex(index:uint):CheckboxController 
		{
			return this.checkboxes[index] as CheckboxController;
		}

		public function getInputControllerByIndex(index:uint):InputController 
		{
			return this.inputs[index] as InputController;
		}

		/**
		 * Zwraca InputController lub TextAreaController ktory odpowiada za walidacje
		 * pola
		 * 
		 * @field Pole tekstowe
		 * @see InputController
		 * @see TextAreaController
		 */
		public function getInputControllerByField(field:TextField):InputController 
		{
			return this.inputsByFields[field] as InputController;
		}

		/**
		 * Waliduje formularz i zwraca true jesli poprawne lub false jesli bledne.
		 * W celu uzyskania szczegolowych informacji o wyniku walidacji nalezy po wywolaniu
		 * validate() odczytac zawartosc tablicy badItems.
		 */
		public function validate():Vector.<DisplayObject> 
		{
			var result:Vector.<DisplayObject>;
			
			var tmp_l:uint = this.items.length;
			
			var im:InputController;
			var cc:CheckboxController;
			
			var errorsItems:Array = [];
			
			for(var i:uint = 0; i < tmp_l; i++) 
			{
				if(this.items[i] is InputController) 
				{
                
					im = this.items[i] as InputController;
				
					if(im.required || im.getInputText( ).length > 0) 
					{
						if(!im.validate( )) 
						{
							if(!result) {
								result = new Vector.<DisplayObject>;
							}
							result.push( im.textField );
							errorsItems.push( im );
						}
					}
				}
                else if(this.items[i] is CheckboxController) 
				{
					cc = this.items[i] as CheckboxController;
                	
					if(cc.required) 
					{
						if(!cc.validate( )) 
						{
							if(!result) {
								result = new Vector.<DisplayObject>;
							}
							result.push( cc.view );
							errorsItems.push( cc );				
						}
					}
				}
			}
			
			this._badItems = errorsItems;
			
			return result;
		}

		/**
		 * Zwraca tablice elementow, ktore nie przeszly pomyslnie walidacji po ostatnim
		 * uzyciu metody validate(). Tablica zawiera liste elementow typu InputController,
		 * TextAreaController lub CheckboxController
		 * 
		 * @see InputController
		 * @see TextAreaController
		 * @see CheckboxController
		 */
		public function get badItems():Array 
		{
			return _badItems;
		}

		public static function trim(source:String, removeChars:String = ' \n\t\r'):String 
		{
			var pattern:RegExp = new RegExp( '^[' + removeChars + ']+|[' + removeChars + ']+$', 'g' );
			return source.replace( pattern, '' );
		}

		public static function removeExtraSpaces(source:String):String 
		{
			var pattern:RegExp = /( )+/g;
			return FormManager.trim( source.replace( pattern, ' ' ), ' ' );
		}
	}
}
